/* **********************************************************************************
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
* This source code is subject to terms and conditions of the Shared Source License
* for DSL Editor PowerToy. A copy of the license can be found in the License.htm file
* at the root of this distribution. If you can not locate the Shared Source License
* for DSL Editor PowerToy, please obtain a copy from: http://www.codeplex.com/dsltreegrideditor/Project/License.aspx.
* By using this source code in any fashion, you are agreeing to be bound by
* the terms of the Shared Source License for DSL Editor PowerToy.
*
* You must not remove this notice, or any other, from this software.
*
* **********************************************************************************/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Text;

using DslModeling = Microsoft.VisualStudio.Modeling;
using DslDiagrams = Microsoft.VisualStudio.Modeling.Diagrams;

using DslEditorPowerToy.Controls.Editors.Special.DomainModelTreeGrid.Data;
using DslEditorPowerToy.Languages;


namespace DslEditorPowerToy.Controls.Editors.Special
{
    /// <summary>
    /// A proxy object to wrap the domain model root element
    /// </summary>
    [TypeDescriptionProvider(typeof(DomainPropertyDescriptionProvider))]
    internal class DomainModelProxy : DomainClassProxy, IDomainModel
    {
        #region Constructors
        public DomainModelProxy(DslModeling.ModelElement rootElement)
            : base(rootElement)
        {
        }
        #endregion

        #region IDomainModel Members

        public string DisplayName
        {
            get
            {
                return this.DomainClass.GetDomainClass().DisplayName;
            }
        }

        public string Name
        {
            get
            {
                return this.DomainClass.GetDomainClass().Name;
            }
        }

        #endregion

        #region IDomainClass Members
        public override IDomainModel DomainModel
        {
            get
            {
                return this;
            }
        }
        #endregion
    }
    /// <summary>
    /// A proxy object to wrap a relationship
    /// </summary>
    [TypeDescriptionProvider(typeof(DomainPropertyDescriptionProvider))]
    internal class DomainRelationshipProxy : DomainElementContainerProxy, IDomainRelationship
    {
        #region Constructors
        public DomainRelationshipProxy(DslModeling.ElementLink relationship)
            : base(relationship)
        {
        }
        #endregion

        #region IDomainRelationship Members

        public IDomainModel DomainModel
        {
            get
            {
                return DomainElementProxy.GetInstance(MetaModelHelper.GetRootElement(this.Relationship)) as IDomainModel;
            }
        }

        public IDomainClass ParentClass
        {
            get
            {
                //Return first linked element (which should be parent)
                return DomainElementProxy.GetInstance(MetaModelHelper.GetEmbeddingParentElement(this.Relationship)) as IDomainClass;
            }
        }

        public DomainElementCollection<IDomainClass> ChildClasses
        {
            get
            {
                //Return second linked element (which should be child)
                List<IDomainClass> classes = new List<IDomainClass>();
                classes.Add(DomainElementProxy.GetInstance(MetaModelHelper.GetEmbeddedChildElement(this.Relationship)) as IDomainClass);

                return new DomainElementCollection<IDomainClass>(classes);
            }
        }

        public Type ClassType
        {
            get
            {
                return typeof(DomainClassProxy);
            }
        }

        #endregion

        #region IDomainElement Members
        public override IDomainElementContainer Parent
        {
            get
            {
                return this.ParentClass;
            }
        }
        #endregion

        #region IDomainElementContainer Members
        public override DomainElementCollection<IDomainElement> Children
        {
            get
            {
                return this.ChildClasses.ConvertAll();
            }
        }
        #endregion

        #region Protected Properties
        protected DslModeling.ElementLink Relationship
        {
            get
            {
                return this.Element as DslModeling.ElementLink;
            }
        }
        #endregion
    }
    /// <summary>
    /// A proxy object to wrap a domain class
    /// </summary>
    [TypeDescriptionProvider(typeof(DomainPropertyDescriptionProvider))]
    internal class DomainClassProxy : DomainElementContainerProxy, IDomainClass
    {
        #region Constructors
        public DomainClassProxy(DslModeling.ModelElement domainClass)
            : base(domainClass)
        {
        }
        #endregion

        #region IDomainClass Members
        public virtual IDomainModel DomainModel
        {
            get
            {
                return DomainElementProxy.GetInstance(MetaModelHelper.GetRootElement(this.DomainClass)) as IDomainModel;
            }
        }
        public virtual IDomainRelationship ParentRelationship
        {
            get
            {
                return DomainElementProxy.GetInstance(MetaModelHelper.GetEmbeddingParentLink(this.DomainClass)) as IDomainRelationship;
            }
        }
        public virtual DomainElementCollection<IDomainRelationship> ChildRelationships
        {
            get
            {
                ReadOnlyCollection<DslModeling.ElementLink> relationships = MetaModelHelper.GetAllEmbeddedRelationships(this.DomainClass);
                if (null == relationships)
                    return null;

                //Wrap collection
                List<IDomainRelationship> proxies = new List<IDomainRelationship>();
                foreach (DslModeling.ElementLink relationship in relationships)
                    proxies.Add(DomainElementProxy.GetInstance(relationship) as IDomainRelationship);

                return new DomainElementCollection<IDomainRelationship>(proxies);
            }
        }
        public virtual Type RelationshipType
        {
            get
            {
                return typeof(DomainRelationshipProxy);
            }
        }
        #endregion

        #region IDomainElement Members
        public override IDomainElementContainer Parent
        {
            get
            {
                return this.ParentRelationship;
            }
        }

        #endregion

        #region IDomainElementContainer Members
        public override DomainElementCollection<IDomainElement> Children
        {
            get
            {
                return this.ChildRelationships.ConvertAll();
            }
        }
        #endregion

        #region Protected Properties
        protected DslModeling.ModelElement DomainClass
        {
            get
            {
                return this.Element;
            }
        }
        #endregion
    }
    /// <summary>
    /// A proxy object to wrap a containing ME
    /// </summary>
    internal abstract class DomainElementContainerProxy : DomainElementProxy, IDomainElementContainer
    {
        static private bool _eventsRegistered = false;

        #region Constructors
        protected DomainElementContainerProxy(DslModeling.ModelElement element)
            : base(element)
        {
            SubscribeToElementEvents(element.Store, element.GetDomainClass().DomainModel);
        }
        #endregion

        #region IDomainElementContainer Members

        public virtual bool AddChildElement(string name, out string validationError)
        {
            validationError = null;
            return false;
        }
        public virtual bool RemoveChildElement(int index)
        {
            return false;
        }
        public virtual bool MoveChildElement(int fromIndex, int toIndex)
        {
            return false;
        }
        public event ChildElementAddedEventHandler ChildElementAdded;
        public event ChildElementRemovedEventHandler ChildElementRemoved;
        public event ChildElementMovedEventHandler ChildElementMoved;
        public abstract DomainElementCollection<IDomainElement> Children
        {
            get;
        }
        #endregion

        #region Event Handlers
        #region Dynamic Delegates
        internal static EventHandler<DslModeling.ElementAddedEventArgs> OnElementAddedHandler = null;
        internal static EventHandler<DslModeling.ElementDeletedEventArgs> OnElementDeletedHandler = null;
        internal static EventHandler<DslModeling.ElementMovedEventArgs> OnElementMovedHandler = null;
        #endregion

        private static void element_Added(object sender, DslModeling.ElementAddedEventArgs e)
        {
            //DomainElementEventManager.AddElementAddedEvent(e);

            //Notify parent instance (if in cache)
            DomainElementProxy element = DomainElementProxy.GetInstance(e.ModelElement);
            DomainElementContainerProxy parent = element.Parent as DomainElementContainerProxy;
            if (null != parent)
                parent.OnChildElementAdded(element);
        }
        private static void element_Deleted(object sender, DslModeling.ElementDeletedEventArgs e)
        {
            //DomainElementEventManager.AddElementDeletedEvent(e);

            //Notify parent instance (if in cache)
            DomainElementProxy element = DomainElementProxy.GetInstance(e.ModelElement);
            DomainElementContainerProxy parent = element.Parent as DomainElementContainerProxy;
            if (null != parent)
                parent.OnChildElementRemoved(element);
        }
        private static void element_Moved(object sender, DslModeling.ElementMovedEventArgs e)
        {
            //Notify parent instance (if in cache)
            DomainElementProxy element = DomainElementProxy.GetInstance(e.ModelElement);
            DomainElementContainerProxy parent = element.Parent as DomainElementContainerProxy;
            if (null != parent)
                parent.OnChildElementMoved(element);
        }
        #endregion

        #region Protected Methods

        /// <summary>
        /// Raises element added event
        /// </summary>
        /// <param name="index"></param>
        protected void OnChildElementAdded(IDomainElement addedElement)
        {
            if (null != this.ChildElementAdded)
                this.ChildElementAdded(this, new ChildElementAddedEventArgs(addedElement));
        }
        /// <summary>
        /// Raises element removed event
        /// </summary>
        /// <param name="index"></param>
        protected void OnChildElementRemoved(IDomainElement deletedElement)
        {
            if (null != this.ChildElementRemoved)
                this.ChildElementRemoved(this, new ChildElementRemovedEventArgs(deletedElement));
        }
        /// <summary>
        /// Raises element moved event
        /// </summary>
        /// <param name="index"></param>
        protected void OnChildElementMoved(IDomainElement movedElement)
        {
            if (null != this.ChildElementMoved)
                this.ChildElementMoved(this, new ChildElementMovedEventArgs(movedElement));
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Subscribes to store events for changes on element
        /// </summary>
        private void SubscribeToElementEvents(DslModeling.Store store, DslModeling.DomainModelInfo domainModelInfo)
        {
            if (true == _eventsRegistered)
                return;
            else
                _eventsRegistered = true;

            if (null == store)
                return;
            
            //Get event manager from element
            DslModeling.EventManagerDirectory eventManager = store.EventManagerDirectory;
            if (null == eventManager)
                return;

            //Initialise delegates
            OnElementAddedHandler = new EventHandler<DslModeling.ElementAddedEventArgs>(element_Added);
            OnElementDeletedHandler = new EventHandler<DslModeling.ElementDeletedEventArgs>(element_Deleted);
            OnElementMovedHandler = new EventHandler<DslModeling.ElementMovedEventArgs>(element_Moved);

            //Registers events for all types
            eventManager.ElementAdded.Add(domainModelInfo, OnElementAddedHandler);
            eventManager.ElementDeleted.Add(domainModelInfo, OnElementDeletedHandler);
            eventManager.ElementMoved.Add(domainModelInfo, OnElementMovedHandler);
        }
        /// <summary>
        /// Unsubscribes to store events for changes on element
        /// </summary>
        private void UnsubscribeToElementEvents(DslModeling.Store store)
        {
            if (null == store)
                return;
            
            //Get event manager from element
            DslModeling.EventManagerDirectory eventManager = store.EventManagerDirectory;
            if (null == eventManager)
                return;

            //Unregister handlers (for all instances of this classes type)
            if (null != OnElementAddedHandler)
                eventManager.ElementAdded.Remove(OnElementAddedHandler);
            if (null != OnElementDeletedHandler)
                eventManager.ElementDeleted.Remove(OnElementDeletedHandler);
            if (null != OnElementMovedHandler)
                eventManager.ElementMoved.Remove(OnElementMovedHandler);
        }
        #endregion

        //#region Private Classes
        //private static class DomainElementEventManager
        //{
        //    static private Stack<DslModeling.ModelElement> _cache = new Stack<DslModeling.ModelElement>();

        //    #region Public Methods
        //    /// <summary>
        //    /// Adds the specified event to a queue until we reach the model root,
        //    /// then playback events in reversed order to parents
        //    /// </summary>
        //    /// <param name="e"></param>
        //    static public void AddElementAddedEvent(DslModeling.ElementAddedEventArgs e)
        //    {
        //        DslModeling.ModelElement addedME = e.ModelElement;
        //        if (addedME is DslDiagrams.PresentationElement)
        //            return;

        //        //Ensure current cache belongs to current element
        //        if (_cache.Count > 0)
        //        {
        //            //Get immediate parent element in tree
        //            DslModeling.ModelElement topMEParent = null;
        //            DslModeling.ModelElement topME = _cache.Peek();
        //            if (topME is DslModeling.ElementLink)
        //                topMEParent = MetaModelHelper.GetEmbeddingParentElement(topME as DslModeling.ElementLink);
        //            else
        //                topMEParent = MetaModelHelper.GetEmbeddingParentLink(topME);

        //            //Check if linked to same cache as last element
        //            if (topMEParent != addedME)
        //            {
        //                //Start a new cache
        //                _cache.Clear();
        //            }
        //        }

        //        //Add element to cache
        //        _cache.Push(addedME);

        //        if (2 == _cache.Count)
        //        {
        //        ////Check to see if the parent is rooted in the tree
        //        //DslModeling.ModelElement addedMEParent = null;
        //        //if (addedME is DslModeling.ElementLink)
        //        //    addedMEParent = MetaModelHelper.GetEmbeddingParentElement(addedME as DslModeling.ElementLink);
        //        //else
        //        //    addedMEParent = MetaModelHelper.GetEmbeddingParentLink(addedME);

        //        ////Check if we have reached a rooted parent
        //        //if (true == MetaModelHelper.IsRootElement(addedMEParent))
        //        //{
        //            //Playback cache
        //            while (_cache.Count > 0)
        //            {
        //                DslModeling.ModelElement topME = _cache.Pop();

        //                //Get proxy for element
        //                DomainElementProxy topElement = DomainElementProxy.GetInstance(topME);
        //                DomainElementContainerProxy parentProxy = topElement.Parent as DomainElementContainerProxy;
        //                //Raise event
        //                if (null != parentProxy)
        //                    parentProxy.OnChildElementAdded(topElement);
        //            }

        //            //Reset
        //            _cache.Clear();
        //        }
        //    }
        //    /// <summary>
        //    /// Adds the specified event to a queue until we reach the last element,
        //    /// then playback events in reversed order to parents
        //    /// </summary>
        //    /// <param name="e"></param>
        //    static public void AddElementDeletedEvent(DslModeling.ElementDeletedEventArgs e)
        //    {
        //    }            
        //    #endregion
        //}
        //#endregion
    }
    /// <summary>
    /// A proxy object to wrap an ME
    /// </summary>
    internal abstract class DomainElementProxy : IDomainProxy, IDomainElement, IDomainPropertyOwner
    {
        private static DomainElementProxyCache _instanceCache = null;
        private DslModeling.ModelElement _element = null;
        static private bool _eventsRegistered = false;

        #region Constructors
        protected DomainElementProxy(DslModeling.ModelElement element)
        {
            this._element = element;

            SubscribeToElementEvents(this._element.Store, this._element.GetDomainClass().DomainModel);
        }
        ~DomainElementProxy()
        {
        }
        #endregion

        #region IDomainPropertyOwner Members

        public virtual string GetValidationMessage(string propertyName, string value)
        {
            return null;
        }

        #endregion

        #region IDomainElement Members
        public event PropertiesUpdatedEventHandler PropertiesUpdated;
        public abstract IDomainElementContainer Parent
        {
            get;
        }

        #endregion

        #region IDomainProxy Members
        public virtual ReadOnlyCollection<DslModeling.DomainPropertyInfo> GetPropertyInfo()
        {
            return MetaModelHelper.GetAllPropertyInfo(this._element);
        }
        public DslModeling.ModelElement Element
        {
            get
            {
                return this._element;
            }
        }
        #endregion

        #region Event Handlers
        #region Dynamic Delegates
        internal static EventHandler<DslModeling.ElementPropertyChangedEventArgs> OnElementPropertyChangedHandler = null;
        #endregion

        /// <summary>
        /// Raise the property changed event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void element_PropertyChanged(object sender, DslModeling.ElementPropertyChangedEventArgs e)
        {
            //Update instance (if in cache)
            DomainElementProxy element = DomainElementProxy.GetInstance(e.ModelElement);
            if (null != element)
                element.OnPropertiesUpdated(element);
        }
        #endregion

        #region Protected Properties
        protected Guid Id
        {
            get
            {
                return this._element.Id;
            }
        }
        #endregion

        #region Protected Methods

        /// <summary>
        /// Raises element updated event
        /// </summary>
        /// <param name="index"></param>
        protected void OnPropertiesUpdated(IDomainElement updatedElement)
        {
            if (null != this.PropertiesUpdated)
                this.PropertiesUpdated(this, new PropertiesUpdatedEventArgs(updatedElement));
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Gets a proxied instance of element
        /// This method maintains a list of singleton instances
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        static public DomainElementProxy GetInstance(DslModeling.ModelElement element)
        {
            if (null == element)
                return null;

            //Ensure cache exists
            if (null == _instanceCache)
                _instanceCache = new DomainElementProxyCache();

            //Check stored list of instances for a match
            DomainElementProxy proxyInstance = _instanceCache.Find(element);
            if (null != proxyInstance)
            {
                //Return existing instance
                return proxyInstance;
            }
            else
            {
                DomainElementProxy newInstance = null;

                //Check type of ME
                if (element is DslModeling.ElementLink)
                    newInstance = new DomainRelationshipProxy(element as DslModeling.ElementLink);
                else
                {
                    if (true == MetaModelHelper.IsRootElement(element))
                        newInstance = new DomainModelProxy(element);
                    else
                        newInstance = new DomainClassProxy(element);
                }

                //Add instance to store
                _instanceCache.Add(newInstance);

                return newInstance;
            }
        }
        public override string ToString()
        {
            return string.Format("(proxied) {0}", this._element.ToString());
        }
        public override bool Equals(object obj)
        {
            if ((null == obj)
                || (!(obj is DomainElementProxy)))
                return false;

            return (this._element == (obj as DomainElementProxy)._element);
        }
        public override int GetHashCode()
        {
            return this._element.GetHashCode();
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Subscribes to store events for changes on element
        /// </summary>
        private void SubscribeToElementEvents(DslModeling.Store store, DslModeling.DomainModelInfo domainModelInfo)
        {
            if (true == _eventsRegistered)
                return;
            else
                _eventsRegistered = true;

            if (null == store)
                return;

            //Get event manager from element
            DslModeling.EventManagerDirectory eventManager = store.EventManagerDirectory;
            if (null == eventManager)
                return;

            //Initialise delegates
            OnElementPropertyChangedHandler = new EventHandler<DslModeling.ElementPropertyChangedEventArgs>(element_PropertyChanged);

            //Registers events for all types
            eventManager.ElementPropertyChanged.Add(domainModelInfo, OnElementPropertyChangedHandler);
        }
        /// <summary>
        /// Unsubscribes to store events for changes on element
        /// </summary>
        private void UnsubscribeToElementEvents(DslModeling.Store store)
        {
            if (null == store)
                return;

            //Get event manager from element
            DslModeling.EventManagerDirectory eventManager = store.EventManagerDirectory;
            if (null == eventManager)
                return;

            //Unregister handlers
            if (null != OnElementPropertyChangedHandler)
                eventManager.ElementPropertyChanged.Remove(OnElementPropertyChangedHandler);
        }
        #endregion

        #region Private Classes
        private class DomainElementProxyCache : List<DomainElementProxy>
        {
            #region Constructors
            public DomainElementProxyCache()
                : base()
            {
            }
            #endregion

            #region Public Methods
            /// <summary>
            /// Finds first instance of element in collection
            /// </summary>
            /// <param name="element"></param>
            /// <returns></returns>
            public DomainElementProxy Find(DslModeling.ModelElement element)
            {
                foreach (DomainElementProxy proxy in this)
                {
                    if (proxy.Element == element)
                        return proxy;
                }

                return null;
            }
            #endregion
        }
        #endregion
    }
}
